﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Automation;
using QuickUIT.Utilities;

namespace QuickUIT
{
    /// <summary>
    /// Parses search expressions into search criteria.  
    /// </summary>
    public static class LocatorParser
    {
        private static readonly char[] GROUP_BRACKET = {'[', ']'};

        /// <summary>
        /// Gets search criteria from a search locator expression. 
        /// </summary>
        /// <param name="locator">The locator.</param>
        /// <returns>Search criteria extacted from the locator.</returns>
        public static IAutomationElementSearchCriteria GetSearchCriteria(string locator)
        {
            ValidationUtility.ValidateStringNotNullOrEmptyOrWhitespace(locator, "locator");

            var searchCriteria = new AutomationElementSearchCriteria();

            string expression = locator; 

            // parse indexes at the end of the locator 
            var indexMatcherRegex = new Regex(@"\[(\d+)\]$");
            Match indexMatch = indexMatcherRegex.Match(locator);
            if (indexMatch.Success)
            {
                searchCriteria.Index = int.Parse(indexMatch.Groups[1].Value);
                expression = indexMatcherRegex.Replace(locator, String.Empty);
            }

            var conditions = new List<string>();


            // parse the remainining search conditions 
            for (int i = 0; i < expression.Length; i++)
            {
                // look for a potential start of a group 
                if (expression[i] == GROUP_BRACKET[0])
                {
                    for (int j = i + 1; j < expression.Length; j++)
                    {
                        // look until the the end of a potential end of group identifier
                        if (expression[j] == GROUP_BRACKET[1])
                        {
                            if (expression[j - 1] == '\'' &&
                                (j + 1 == expression.Length ||
                                 expression[j + 1] == GROUP_BRACKET[0]))
                            {
                                conditions.Add(expression.Substring(i, j - i + 1));
                                i = j + 1;
                            }
                        }
                    }
                }
            }

            if (conditions.Count > 0)
            {
                foreach (string condition in conditions)
                {
                    searchCriteria.Add(GetSearchCondition(condition));
                }
            }
            else
            {
                var regex = new Regex(@"^\s*$");
                if (!regex.IsMatch(expression))
                {
                    searchCriteria.Add(GetSearchCondition(expression));
                }
            }
            return searchCriteria;
        }

        private static IAutomationElementSearchCondition GetSearchCondition(string locator)
        {
            ValidationUtility.ValidateArgumentNotNull(locator, "locator");

            string propValPair = locator.Trim(new[] {'[', ']'});
            var propertyMatcherRegex = new Regex(@"^@([\w]+)='(.*)'$");
            Match propertyMatch = propertyMatcherRegex.Match(propValPair);

            if (propertyMatch.Success)
            {
                string property = propertyMatch.Groups[1].Value + "Property";
                string value = propertyMatch.Groups[2].Value;
                object automationValue = value;
                var automationProperty =
                    (typeof (AutomationElement)).GetField(property).GetValue(null) as AutomationProperty;
                if (automationProperty == AutomationElementIdentifiers.ControlTypeProperty)
                {
                    automationValue = GetControlType(value);
                }

                IAutomationElementSearchCondition searchCondition;
                if (TryGetRegexSearchCondition(propValPair, automationProperty, out searchCondition))
                {
                    return searchCondition;
                }
                if (TryGetContainsSearchCondition(propValPair, automationProperty, out searchCondition))
                {
                    return searchCondition;
                }
                return new AutomationElementExactSearchCondition(automationProperty, automationValue);
            }
            throw new ArgumentException(
                String.Format(
                    "'{0}' is an invalid property condition. Property conditions must be in the format '@<PropertyName>='<PropertyValue>''",
                    locator));
        }

        private static bool TryGetRegexSearchCondition(string locator, AutomationProperty property,
                                                       out IAutomationElementSearchCondition searchCondition)
        {
            searchCondition = null;
            var regexMatcher = new Regex(@"^@([\w]+)='/(.*)/'$");
            if (regexMatcher.IsMatch(locator))
            {
                Match match = regexMatcher.Match(locator);
                string value = match.Groups[2].Value;
                searchCondition = new AutomationElementRegexSearchCondition(property, new Regex(value));
                return true;
            }
            return false;
        }

        private static bool TryGetContainsSearchCondition(string locator, AutomationProperty property,
                                                          out IAutomationElementSearchCondition searchCondition)
        {
            searchCondition = null;
            var regexMatcher = new Regex(@"^@([\w]+)='{(.*)}'$");
            if (regexMatcher.IsMatch(locator))
            {
                Match match = regexMatcher.Match(locator);
                string value = match.Groups[2].Value;
                searchCondition = new AutomationElementContainsSearchCondition(property, value);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Gets a <see cref="ControlType"/> from a string.  
        /// </summary>
        /// <param name="controlType">The control type to parse.</param>
        /// <returns>The <see cref="ControlType"/> from the string.</returns>
        public static ControlType GetControlType(string controlType)
        {
            ValidationUtility.ValidateStringNotNullOrEmptyOrWhitespace(controlType, "controlType");
            return typeof (ControlType).GetField(controlType).GetValue(null) as ControlType;
        }
    }
}